﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;

using Fiddler;
using weber.Base;

namespace weber
{
	/// <summary>
	/// helper methods and data
	/// </summary>
	internal static class Helper
	{
		const string ProxyServerIPAddress = "127.0.0.1";
		const int ProxyServerPortNumber = 9898;

		/// <summary>
		/// list of sessions aborted by the browser
		/// </summary>
		internal static List<Session> AbortedSessions = new List<Session>();

		/// <summary>
		/// sessions that went through
		/// </summary>
		internal static List<Session> AllowedSessions = new List<Session>();

		/// <summary>
		/// app init
		/// </summary>
		internal static void AppInit()
		{
			// attach fiddler proxy server
			Fiddler.FiddlerApplication.Startup(ProxyServerPortNumber, FiddlerCoreStartupFlags.Default);
			Fiddler.FiddlerApplication.BeforeRequest += new SessionStateHandler(FiddlerApplication_BeforeRequest);
			Fiddler.FiddlerApplication.AfterSessionComplete += new SessionStateHandler(FiddlerApplication_AfterSessionComplete);
		}

		/// <summary>
		/// after the session (request) completes
		/// </summary>
		static void FiddlerApplication_AfterSessionComplete(Session oSession)
		{
			// is the document moved (either permanently or temporarily)
			if (301 == oSession.responseCode || 302 == oSession.responseCode)
			{
				// yes, is the fiddler session (request) originated by current process
				if (oSession.LocalProcessID == CurrentProcessID)
				{
					// yes, we need to add the redirection url to the Requests list

					// yes, create a request info based on the url
					RequestInfo info = new RequestInfo(Guid.Empty, new Uri(oSession.fullUrl));

					// get the original request using this url
					RequestInfo req = GetRequestByUrlKey(info.UrlKey);
					if (req != null)
					{
						// original request exists. delete it.
						// add a new request with the redirected url
						string redirectedUrl = oSession.oResponse.headers["location"];
						Guid browserId = req.BrowserId;
						DeleteRequest(browserId);
						SaveRequest(browserId, new Uri(redirectedUrl));
					}
				}
			}
		}

		static int CurrentProcessID = System.Diagnostics.Process.GetCurrentProcess().Id;
		/// <summary>
		/// inspect every request from this application
		/// </summary>
		static void FiddlerApplication_BeforeRequest(Session oSession)
		{
			// is the fiddler session (request) originated by current process
			if (oSession.LocalProcessID == CurrentProcessID)
			{
				// yes, create a request info based on the url
				RequestInfo info = new RequestInfo(Guid.Empty, new Uri(oSession.fullUrl));

				// get the original request using this url
				RequestInfo req = GetRequestByUrlKey(info.UrlKey);

				// is there an original request?
				if (req == null)
				{
					// no, original request with the same url key doesn't exist. abort.
					oSession.Abort();

					// log
					AbortedSessions.Add(oSession);
				}
				else
				{
					// yes, exists. allow. log
					AllowedSessions.Add(oSession);
				}
			}
		}

		/// <summary>
		/// cleanup when the application exits
		/// </summary>
		internal static void AppCleanup()
		{
			// delete wininet cache items
			Fiddler.WinINETCache.ClearCacheItems(true, true);

			// detach fiddler proxy
			FiddlerApplication.oProxy.Detach();
		}

		static List<RequestInfo> Requests = new List<RequestInfo>();
		static object _lockObj = new object();
		/// <summary>
		/// create the request info for the given request (uri) and browser, and save it to global store
		/// </summary>
		public static void SaveRequest(Guid browserId, Uri uri)
		{
			if (uri.HostNameType != UriHostNameType.Unknown && !string.IsNullOrWhiteSpace(uri.Host))
			{
				string au = uri.AbsoluteUri.ToLower();
				if (!(au.Contains("unknownprotocol.htm") || au.Contains("navcancl.htm")))
				{
					RequestInfo info = GetRequestByBrowserId(browserId);
					if (info == null)
					{
						info = new RequestInfo(browserId, uri);
						Requests.Add(info);
					}
				}
			}
		}

		/// <summary>
		/// delete the request for the given browser from the global store
		/// </summary>
		internal static void DeleteRequest(Guid browserId)
		{
			lock (_lockObj)
			{
				RequestInfo info = Requests.Where(x => x.BrowserId == browserId).FirstOrDefault();
				if (info != null)
				{
					Requests.Remove(info);
				}
			}
		}

		/// <summary>
		/// get the request for the given browser id
		/// </summary>
		internal static RequestInfo GetRequestByBrowserId(Guid browserId)
		{
			RequestInfo result = null;
			lock (_lockObj)
			{
				result = Requests.Where(x => x.BrowserId == browserId).FirstOrDefault();
			}
			return result;
		}

		/// <summary>
		/// get the request for the given url key
		/// </summary>
		internal static RequestInfo GetRequestByUrlKey(string urlKey)
		{
			RequestInfo result = null;
			lock (_lockObj)
			{
				result = Requests.Where(x => string.Compare(x.UrlKey, urlKey, true) == 0).FirstOrDefault();
			}
			return result;
		}
	}

	/// <summary>
	/// information about a http request
	/// </summary>
	public class RequestInfo
	{
		/// <summary>
		/// unique id of the browser making the request
		/// </summary>
		public Guid BrowserId { get; private set; }

		/// <summary>
		/// url that should be checked
		/// </summary>
		public string UrlKey { get; private set; }

		/// <summary>
		/// number of segments to be checked
		/// </summary>
		public int SegmentsToCheck { get; private set; }

		/// <summary>
		/// individual segments
		/// </summary>
		public string[] Segments { get; private set; }

		/// <summary>
		/// the host url
		/// </summary>
		public string Host { get; private set; }

		/// <summary>
		/// the request uri
		/// </summary>
		public Uri RequestUri { get; set; }

		/// <summary>
		/// overload
		/// </summary>
		public RequestInfo(Guid browserId, Uri uri)
		{
			RequestUri = uri;
			BrowserId = browserId;
			Host = uri.Host.ToLower();
			SegmentsToCheck = 2; // check the top 2 segments of the url (website.tld)
			Segments = Host.Split('.');
			if (Segments.Length == 1)
			{
				// 1 = most likely visiting an intranet url
				SegmentsToCheck = Segments.Length;
			}
			else if (Segments.Length > 2)
			{
				// 3 or more
				// check the tld to see if its a country domain
				string tld = Segments[Segments.Length - 1].ToUpper();
				if (Constants.CountryCodes.Contains(tld))
				{
					// yes, check top 3 segments of the url
					SegmentsToCheck = 3;
				}
			}

			// form the url to check from the host name
			StringBuilder sb = new StringBuilder();
			for (int i = Segments.Length - SegmentsToCheck; i < Segments.Length; i++)
			{
				sb.Append(Segments[i]);
			}
			UrlKey = sb.ToString();
		}
	}
}
